#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 
# md5fwalker.py
#
# Copyright (c) 2008, Andrey Usov <uandrey@gmail.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of the <organization> nor the
#       names of its contributors may be used to endorse or promote products
#       derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY <copyright holder> ''AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


import os, sys, time
from hashlib import md5

__version = "0.7"

def get_filedata(filename):
    filedata = {}
    try:
        file = open(filename, 'r')
        while 1:
            line = file.readline()
            if line[-1:] == "\n": line = line[:-1]
            if not line: break
                
            fields = line.split("\t")
            filedata[fields[3]] = (fields[0], fields[1], fields[2])
        file.close()
    except IOError:
        pass
    return filedata

def md5filecalc(filename):
    """ md5 calculation for file """
    try:
        file = open(filename, 'rb')
        md5handler = md5()
        chunk_size = 2**16 # chunk size of file
        while 1:
            chunk = file.read(chunk_size)
            if not chunk: break
            md5handler.update(chunk)
        file.close()
    except IOError:
        return None
    return md5handler.hexdigest()

def get_dtfile(filename):
    """ get datetime of file """
    try:
        dt = time.localtime(os.stat(filename)[8])
    except:
        return "0000-00-00 00:00:00"
    return "%d-%02d-%02d %02d:%02d:%02d" % (dt[0],dt[1],dt[2],dt[3],dt[4],dt[5])

def get_filesize(filename):
    """ get file size """
    return os.stat(filename)[6]

def md5calc(path, filesdata, show_new_files):
    """ main script """
    fileList = []
    md5data = []
    db_new_files = []
    rootdir = path
    total_files = 0
    new_files = 0
    
    try:
        for root, subFolders, files in os.walk(rootdir):
            for file in files:
                fileList.append(os.path.join(root,file))
            for filename in fileList: 
                if filename in filesdata:
                    fmd5hash = filesdata[filename][0]
                    fdatetime = filesdata[filename][1]
                    fsize = int(filesdata[filename][2])
                    
                    if (fdatetime != get_dtfile(filename)) or (fsize != get_filesize(filename)):
                        md5fileDigest = md5filecalc(filename)
                        if md5fileDigest: 
                            f_info = "%s\t%s\t%d\t%s" % (md5fileDigest, get_dtfile(filename), \
                                                        get_filesize(filename), filename)
                            md5data.append(f_info)
                            db_new_files.append(f_info)
                        total_files += 1
                        new_files += 1
                    else:
                        md5data.append("%s\t%s\t%s\t%s" % (fmd5hash, fdatetime, fsize, filename))
                        total_files += 1
                else:
                    md5fileDigest = md5filecalc(filename)
                    if md5fileDigest: 
                        md5data.append("%s\t%s\t%d\t%s" % (md5fileDigest, get_dtfile(filename), \
                                                    get_filesize(filename), filename))
                    total_files += 1
                    new_files += 1
                sys.stderr.write("total/new files: %d/%d\r" % (total_files, new_files))
            fileList = []
        # show new files
        if show_new_files: 
            for f_info in db_new_files: print f_info 
    except KeyboardInterrupt:
        print "The process terminated by user"
    return md5data
    
def save_md5data(filename, md5data):
    """ save md5data to file """
    file = open(filename, "w")
    for line in md5data:
        file.write(line + "\n")
    file.close()

if __name__ == "__main__":
    from optparse import OptionParser
    
    parser = OptionParser(version="%prog, v." + __version)
    parser.add_option("-p", "--path", dest="path", help="path for searching of files")
    parser.add_option("-m", "--md5data", dest="md5data", help="filename for storing md5 data")
    parser.add_option("-n", "--show_new_files", dest="show_new_files", \
                        action="store_true", help="show new files")
    (options, args) = parser.parse_args()
    
    if (options.path == None) or (options.md5data == None):
        parser.error("Incorrect number of arguments, \n\nuse -h or --help for detail")
    
    md5data_res = md5calc(options.path, get_filedata(options.md5data), options.show_new_files)
    save_md5data(options.md5data, md5data_res)
    
